#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

RotatedRect FindLicense(const Mat& input);
void SplitCharacters(const Mat& input, vector<Mat>& output);

void GrayscaleSegmentation(const Mat& input, Mat& output, float rate);
void AdaptiveThreshold(const Mat& input, Mat& output, double rate);


int main(int argc, char** argv)
{
    Mat src = imread("img/3.png");
    RotatedRect r_rect = FindLicense(src);

    Mat lic_r;
    Rect lic_rect = r_rect.boundingRect();
    warpAffine(src(lic_rect), lic_r, getRotationMatrix2D(lic_rect.tl()/2, r_rect.angle - 90, 1), lic_rect.size());
    vector<Mat> characters;
    SplitCharacters(lic_r, characters);

    imshow("src", src);
    imshow("lic", lic_r);
    if(characters.size() == 7)
    {
        imshow("0", characters[0]);
        imshow("1", characters[1]);
        imshow("2", characters[2]);
        imshow("3", characters[3]);
        imshow("4", characters[4]);
        imshow("5", characters[5]);
        imshow("6", characters[6]);
    }
    waitKey();
    return 0;
}

RotatedRect FindLicense(const Mat& input)
{
    Mat img = input.clone();
    GrayscaleSegmentation(img, img, 0.8);
    threshold(img, img, 70, 255, THRESH_BINARY);
    dilate(img, img, getStructuringElement(MORPH_RECT, Size(3, 3)), Point(-1, -1), 2);
    erode(img, img, getStructuringElement(MORPH_RECT, Size(3, 3)), Point(-1, -1), 1);
    vector<vector<cv::Point>> vpp;
    findContours(img, vpp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

// TODO: 添加边缘检测与色彩检测共同评估, 或需要将色彩检测与边缘检测分离

//    Mat o1, o2;
//    Mat kernel = (Mat_<float>(2, 2) << 1, 0, 0, -1);
//    filter2D(output, o1, -1, kernel);
//    kernel = (Mat_<float>(2, 2) << 0, 1, -1, 0);
//    filter2D(output, o2, -1, kernel);
//    output = o1+o2;

    RotatedRect max_r_rect;
    for(auto& i :vpp)
    {
        RotatedRect r_rect = minAreaRect(i);
        if(r_rect.size.area() > max_r_rect.size.area())
        {
            max_r_rect = r_rect;
        }
    }

    return max_r_rect;
}

void SplitCharacters(const Mat& input, vector<Mat>& output)
{
    // 归一化
    Mat src = input.clone();
    resize(src, src, Size(110, 35));
    cvtColor(src, src, COLOR_BGR2GRAY);
    AdaptiveThreshold(src, src, 1.2);

    // 横向投票, 得到列向量, 取最宽
    Mat v_vector(Size(1, 35), CV_32F, Scalar(0));
    reduce(src, v_vector, 1, REDUCE_SUM, CV_MAKE_TYPE(CV_32F, 32)); // NOLINT(hicpp-signed-bitwise)
    v_vector.convertTo(v_vector, CV_8UC1, 0.01); // NOLINT(hicpp-signed-bitwise)
    threshold(v_vector, v_vector, 50, 255, THRESH_BINARY);
    vector<vector<Point>> v_vvp;
    findContours(v_vector, v_vvp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    Rect max_rect;
    for(auto& i : v_vvp)
    {
        Rect rect = boundingRect(i);
        if(rect.area() > max_rect.area())
        {
            max_rect = rect;
        }
    }
    src = src(Rect(max_rect.tl(), Point(110, max_rect.br().y)));
    resize(src, src, Size(110, 35));

    // 纵向投票, 取出每一个字符
    Mat h_vector(Size(110, 1), CV_32F, Scalar(0));
    reduce(src, h_vector, 0, REDUCE_SUM, CV_MAKE_TYPE(CV_32F, 32)); // NOLINT(hicpp-signed-bitwise)
    h_vector.convertTo(h_vector, CV_8UC1, 0.03); // NOLINT(hicpp-signed-bitwise)
    threshold(h_vector, h_vector, 20, 255, THRESH_BINARY);
    vector<vector<Point>> h_vvp;
    findContours(h_vector, h_vvp, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    vector<Mat> result(7);
    int index = 7;
    for(auto& i : h_vvp)
    {
        Rect rect = boundingRect(i);

        if(rect.area() < 5)
        {
            continue;
        }
        Mat character = src(Rect(rect.tl(), Point(rect.br().x, 35)));
        resize(character, character, Size(20, 20));
        index--;
        if(index < 0)
        {
            break;
        }
        result[index] = character;
    }

    // 输出
    result.swap(output);
}

// 针对蓝色区域的特殊灰度化
void GrayscaleSegmentation(const Mat& input, Mat& output, float rate)
{
    Mat result;
    if(input.channels() == 1)
    {
        result = input;
        output = result;
    }
    Mat bgr_channel[3];
    split(input, bgr_channel);
    Mat b_r = bgr_channel[0] - bgr_channel[2];
    Mat b_g = bgr_channel[0] - bgr_channel[1];
    Mat gray;
    cvtColor(input, gray, COLOR_BGR2GRAY);
    result = (b_r / 2 + b_g / 2) * rate + gray * (1 - rate);
    output = result;
}

void AdaptiveThreshold(const Mat& input, Mat& output, double rate)
{
    Mat src = input.clone();
    int height = (int)sqrt(double(src.rows * (src.cols + src.rows)) / double(src.cols));
    int width = src.cols * height / src.rows;
    for(int i = 0; i < src.rows; i++)
        for (int j = 0; j < src.cols; j++)
        {
            int h1 = max(1, i - height / 2);
            int h2 = max(1, i + height / 2);
            int w1 = min(j - width / 2, src.cols);
            int w2 = min(j + width / 2, src.cols);
            double avg = 0;
            for (int x = h1; x < h2; x++)
                for (int y = w1; y < w2; y++)
                    avg += (double) src.at<uint8_t>(x, y);
            src.at<uint8_t>(i, j) = uint8_t(avg / ((w2 - w1) * (h2 - h1)));
        }

    for(int i = 0; i< src.rows; i++)
        for(int j = 0; j < src.cols; j++)
            src.at<uint8_t>(i, j) = input.at<uint8_t>(i, j) < (src.at<uint8_t>(i, j) * rate) ? 0 : 255;
    output = src;
}
